先說個題外話,Spring 1.0.0 M1 最大的改變應該就是命名吧XD,StructuredOutputConverter
在 0.8 版稱為 OutputParser
,Spring 認為這功能沒任何解析的作用,只是做了轉換,所以跟其他模組的轉換器採用同樣的命名原則,改為StructuredOutputConverter
,當初應該是為了搶快,所以名稱都抄 LangChain4j 的吧
對程式而言,要看懂 ChatGPT 回應的文字並不容易,所以 OpenAI 也提供了許多方式將回應轉為不同型態,JSON、XML 或是 Markdown 的語法,最近 OpenAI 還更新了新的功能,透過強制約束,讓 OpenAI 的輸出可以與定義的 JSON 格式完全一致,有了這功能後進行外部系統整合才能讓失誤率降到最低
下面是目前 StructuredOutputConverter
的資料流程,不過我相信馬上也會針對 OpenAI 的新功能進行改版
可以看到流程中主要還是透過 Prompt 的方式讓 AI 依據描述的內容輸出成需要的格式,凱文大叔猜測這一塊之後只需放上要輸出的 Class,就能引導 AI 照格式來輸出
Spring AI 目前提供的轉換器有三個BeanOutputConverter
、 MapOutputConverter
和ListOutputConverter
(抽象類別就不看了)
BeanOutputConverter
:AI 產生的格式化資料主要以 JSON 為主,這個轉換器就是將 JSON 轉為 Java 程式需要的 Bean,背後用到的就是 Spring MVC 最常用到的 ObjectMapper
MapOutputConverter
:這個轉換器是將資料使用 Map 的方式轉出,對未知的格式最常使用的處理方式
ListOutputConverter
:這個轉換器顧名思義就是將結果轉為 List,不過這裡主要以字串的 List 為主,例如請 AI 提供最受歡迎的五種冰淇淋口味,若是結構化的複數資料則還是使用 BeanOutputConverter
進行轉換,只是將 Bean 的類別改為 ParameterizedTypeReference
我們將寫一隻電影百科機器人,當詢問影星時還要列出其作品,資料結構如下
record ActorsFilms(String actor, List<String> movies) {
}
以下是BeanOutputConverter
的使用方式
BeanOutputConverter<ActorsFilms> beanOutputConverter =
new BeanOutputConverter<>(ActorsFilms.class);
String format = beanOutputConverter.getFormat();
String actor = "Tom Hanks";
String template = """
Generate the filmography of 5 movies for {actor}.
{format}
""";
Generation generation = chatModel.call(
new Prompt(new PromptTemplate(template, Map.of("actor", actor, "format", format)).createMessage())).getResult();
ActorsFilms actorsFilms = beanOutputConverter.convert(generation.getOutput().getContent());
可以看出轉換器就是將 ActorsFilms 轉成 JSON 格式,再放入 Prompt 要求 AI 將結果轉為此格式
如果要問多位影星,程式就改成如下
BeanOutputConverter<List<ActorsFilms>> outputConverter = new BeanOutputConverter<>(
new ParameterizedTypeReference<List<ActorsFilms>>() { });
String format = outputConverter.getFormat();
String template = """
Generate the filmography of 5 movies for Tom Hanks and Bill Murray.
{format}
""";
Prompt prompt = new Prompt(new PromptTemplate(template, Map.of("format", format)).createMessage());
Generation generation = chatModel.call(prompt).getResult();
List<ActorsFilms> actorsFilms = outputConverter.convert(generation.getOutput().getContent());
MapOutputConverter mapOutputConverter = new MapOutputConverter();
String format = mapOutputConverter.getFormat();
String template = """
Provide me a List of {subject}
{format}
""";
PromptTemplate promptTemplate = new PromptTemplate(template,
Map.of("subject", "an array of numbers from 1 to 9 under they key name 'numbers'", "format", format));
Prompt prompt = new Prompt(promptTemplate.createMessage());
Generation generation = chatModel.call(prompt).getResult();
Map<String, Object> result = mapOutputConverter.convert(generation.getOutput().getContent());
ListOutputConverter listOutputConverter = new ListOutputConverter(new DefaultConversionService());
String format = listOutputConverter.getFormat();
String template = """
List five {subject}
{format}
""";
PromptTemplate promptTemplate = new PromptTemplate(template,
Map.of("subject", "ice cream flavors", "format", format));
Prompt prompt = new Prompt(promptTemplate.createMessage());
Generation generation = this.chatModel.call(prompt).getResult();
List<String> list = listOutputConverter.convert(generation.getOutput().getContent());
今天學到的內容:
今日程式碼: https://github.com/kevintsai1202/SpringBoot-AI-Day14.git
凱文大叔使用 Java 開發程式超過 20 年,對於 Java 生態非常熟悉,曾使用反射機制開發 ETL 框架,對 Spring 背後的原理非常清楚,目前以 Spring Boot 作為後端開發框架,前端使用 React 搭配 Ant Design
下班之餘在 Amazing Talker 擔任程式語言講師,並獲得學員的一致好評
最近剛成立一個粉絲專頁-凱文大叔教你寫程式 歡迎大家多追蹤,我會不定期分享實用的知識以及程式開發技巧
想討論 Spring 的 Java 開發人員可以加入 FB 討論區 Spring Boot Developer Taiwan
我是凱文大叔,歡迎一起加入學習程式的行列